package br.feevale.jogo.view;

import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.List;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;

import br.feevale.jogo.components.ComboPortas;
import br.feevale.jogo.components.GamePorta;
import br.feevale.jogo.model.Casa;
import br.feevale.jogo.model.Jogada;
import br.feevale.jogo.model.Tabuleiro;
import br.feevale.jogo.util.AgenteEnum;
import br.feevale.jogo.util.CommandEnum;
import br.feevale.jogo.util.JogadaValida;
import br.feevale.jogo.util.SincronizaAgentes;

@SuppressWarnings("serial")
public class GameView extends JFrame {
	
	private static boolean debugging = true;
	
	private static Integer COLUMNS = 7;
	private static Integer ROWS = 7;
	private static final Integer JOGADA_SIMPLES = 1;
	private static final Integer JOGADA_ELIMINATORIA = 2;
	
	private static final String COMMAND_SEPARATOR = ":";
	private static final String ELIMINATORIA = "-";
	
	private JButton btnRaposa;
	private JButton btnGansos;
	
	private ComboPortas comboPortas;
	private JButton btnPorta;
	private GamePorta porta;
	
	private Tabuleiro tabuleiro;
	
	private AgenteEnum agente;
	private AgenteEnum agenteAdversario;
	
	private JLabel lblAgente;
	private JLabel lblJogadaAtual;
	
	public boolean setupAgente = false;
	public boolean setupAgenteAdversario = false;
	
	private Jogada jogada;
	private AgenteEnum agenteJogada;
	
	private boolean raposaSelecionada = false;
	
	private SincronizaAgentes threadSincronia = null;
	
	private Integer qtdGansosTabuleiro = 0;
	
	private boolean reenvio = true;
	
	private boolean fimJogo = false;
	
	public GameView() {
		tabuleiro = new Tabuleiro(ROWS, COLUMNS);
		
		setTitle("Raposa e as Galinhas");
		setSize(505, 650);
		setLayout(null);
		
		initLayout();
		
		setResizable(false);
		setVisible(true);
		setDefaultCloseOperation(DISPOSE_ON_CLOSE);
	}
	
	private void initLayout() {
		initConfiguracao();
		setAgenteJogada(AgenteEnum.GANSO);
	}
	
	private void initConfiguracao() {
		JPanel pnl = new JPanel();
		pnl.setBorder(BorderFactory.createTitledBorder("Configuração"));
		pnl.setBounds(0, 0, 500, 100);
		pnl.setLayout(null);
		
		JLabel lblPortas = new JLabel("Porta:");
		lblPortas.setBounds(10, 20, 100, 30);
		add(lblPortas);
		
		comboPortas = new ComboPortas();
		comboPortas.setBounds(70, 20, 100, 30);
		add(comboPortas);
		
		btnPorta = new JButton("Ok");
		btnPorta.setBounds(180, 20, 50, 30);
		btnPorta.addActionListener(new ActionListener() {
			
			@Override
			public void actionPerformed(ActionEvent e) {
				porta = new GamePorta(comboPortas.getSelectedItem().toString(), 0, 900, GameView.this);
				
				setSetupAgente(true);
				
				sendCommand(CommandEnum.BTN_PORTA);
				
				debug("INFORMADOS: "+ setupAgente + " - " + setupAgenteAdversario);
				
				if (!(setupsInformados())) {
					threadSincronia = new SincronizaAgentes(GameView.this);
					threadSincronia.start();
				}
				
				initTabuleiro();
				
				btnPorta.setEnabled(false);
				comboPortas.setEnabled(false);
			}
		});
		
		pnl.add(btnPorta);
		
		btnRaposa = new JButton("Raposa");
		btnRaposa.setBounds(10, 60, 105, 30);
		btnRaposa.setEnabled(false);
		btnRaposa.addActionListener(new ActionListener() {
			
			@Override
			public void actionPerformed(ActionEvent e) {
				sendCommand(CommandEnum.BTN_RAPOSA);
				setupAgentes(AgenteEnum.RAPOSA, AgenteEnum.GANSO);
			}
		});
		
		pnl.add(btnRaposa);
		
		btnGansos = new JButton("Gansos");
		btnGansos.setBounds(125, 60, 105, 30);
		btnGansos.setEnabled(false);
		btnGansos.addActionListener(new ActionListener() {
			
			@Override
			public void actionPerformed(ActionEvent e) {
				sendCommand(CommandEnum.BTN_GANSO);
				setupAgentes(AgenteEnum.GANSO, AgenteEnum.RAPOSA);
			}
		});
		
		pnl.add(btnGansos);
		
		JLabel lblJogador = new JLabel("Jogador:");
		lblJogador.setBounds(240, 60, 60, 30);
		pnl.add(lblJogador);
		
		lblAgente = new JLabel();
		lblAgente.setBounds(300, 60, 60, 30);
		pnl.add(lblAgente);
		
		JLabel lblJogada = new JLabel("Jogada:");
		lblJogada.setBounds(370, 60, 60, 30);
		pnl.add(lblJogada);
		
		lblJogadaAtual = new JLabel();
		lblJogadaAtual.setBounds(430, 60, 60, 30);
		pnl.add(lblJogadaAtual);
		
		add(pnl);
	}
	
	private void populaGansos() {
		for (int i = 0; i < tabuleiro.getRows(); i++) {
			for (int j = 0; j < tabuleiro.getColumns(); j++) {
				if (i >= 4) {
					Casa casa = tabuleiro.getCasas()[i][j];
					
					if (casa.getHabilitado()) {
						casa.setAgente(AgenteEnum.GANSO);
						qtdGansosTabuleiro++;
					}
				}
			}
		}
	}
	
	private void initTabuleiro() {
		JPanel pnlTabuleiro = new JPanel();
		pnlTabuleiro.setLayout(new GridLayout(ROWS, COLUMNS));
		pnlTabuleiro.setBounds(0, 120, 500, 500);
		
		int idx = 1;
		
		for (int i=0; i < tabuleiro.getRows(); i++) {
			for (int j=0; j < tabuleiro.getColumns(); j++) {
				Casa casa = getCasa(i, j);
				casa.setId(idx);
				
				tabuleiro.getCasas()[i][j] = casa;
				
				pnlTabuleiro.add(casa);
				
				idx++;
			}
		}
		
		add(pnlTabuleiro);
		
		revalidate();
	}
	
	private void setRaposaTabuleiro(Casa casa) {
		if (posicaoValida(casa.getRow(), casa.getColumn())) {
			casa.setAgente(AgenteEnum.RAPOSA);
			sendCommand(CommandEnum.SETUP_RAPOSA);
			raposaSelecionada = true;
			
			setAgenteJogada(AgenteEnum.GANSO);
		} else {
			JOptionPane.showMessageDialog(this, "Posição inválida", "Posição destinada aos gansos !", 1);
		}
	}
	
	private Casa getCasa(int i, int j) {
		Casa casa = new Casa();
		casa.setRow(i);
		casa.setColumn(j);
		casa.setBorder(BorderFactory.createEtchedBorder());
		
		boolean enabled = isVisible(i, j);
		casa.setHabilitado(enabled);
		casa.setVisible(enabled);
		
		casa.addMouseListener(new MouseListener() {
			
			@Override
			public void mousePressed(MouseEvent e) {
				debug(agente);
				debug(agenteJogada);
				debug(agentesInformados());
				
				Casa casa = (Casa) e.getComponent();
				
				if (!fimJogo) {
					if (raposaSelecionada) {
						debug("Agente: " + getAgente());
						debug("Jogada: " + getAgenteJogada());
						
						if (getAgente().equals(getAgenteJogada())) {
							jogada = new Jogada();
							jogada.setOrigem(casa);
							jogada.setDestino(casa);
						} else {
							JOptionPane.showMessageDialog(GameView.this, "É a jogada do seu adversário", "Aguarde...", 1);
						}
					} else if (AgenteEnum.RAPOSA.equals(getAgente())) {
						setRaposaTabuleiro(casa);
					}
				}
			}
			
			@Override
			public void mouseReleased(MouseEvent e) {
				if (!fimJogo) {
					if (agentesInformados() && getAgente().equals(getAgenteJogada())) {
						Casa casa = (Casa) e.getComponent();
						
						if (jogada != null) {
							if (casa.getHabilitado()) {
								processaJogada();
							}
						}
					}
				}
			}
			
			@Override
			public void mouseEntered(MouseEvent e) {
				if (!fimJogo) {
					if (agentesInformados() && getAgente().equals(getAgenteJogada())) {
						Casa casa = (Casa) e.getComponent();
						
						if (jogada != null) {
							jogada.setDestino(casa);
						}
					}
				}
			}
			
			@Override
			public void mouseExited(MouseEvent e) {}
			
			@Override
			public void mouseClicked(MouseEvent e) {}
		});
		
		return casa;
	}

	private void processaJogada() {
		debug("Jogada - (" + jogada.getOrigem().getId() + ", " + jogada.getDestino().getId() + ")");
		
		JogadaValida jogadaValida = validaJogada();
		
		if (jogadaValida.isValida()) {
			moveAgentes(jogada);
			
			if (jogada.getEliminada() == null) {
				setAgenteJogada(getAgenteAdversario());
			}
			
			sendCommand(CommandEnum.JOGADA);
			verificaFimJogo();
		} else {
			JOptionPane.showMessageDialog(this, jogadaValida.getMensagem(), "Jogada inválida", 1);
		}
	}
	
	/**
	 * Verifica o fim do jogo
	 */
	private void verificaFimJogo() {
		if (AgenteEnum.GANSO.equals(getAgente())) {
			Casa casa = getCasaRaposa();
			
			if (raposaBloqueada(casa)) {
				setFimJogo();
			}
			
		} else if (AgenteEnum.RAPOSA.equals(getAgente())) {
			if (qtdGansosTabuleiro < 4) {
				setFimJogo();
			}
		}
	}
	
	private void setFimJogo() {
		sendCommand(CommandEnum.FIM_JOGO);
		JOptionPane.showMessageDialog(this, "Você venceu", "Fim de jogo", 1);
		
		fimJogo = true;
	}
	
	/**
	 * Verifica se a raposa, em sua posição atual, não encontra-se bloqueada
	 * @param casa
	 * @return
	 */
    private boolean raposaBloqueada(Casa casa) {
    	List<Casa> adjacentes = new ArrayList<Casa>();
    	
    	for (int i = (casa.getRow() - 2 < 0 ? 0 : casa.getRow() - 2); i < (casa.getRow() + 2 > ROWS ? ROWS : casa.getRow() + 2); i++) {
			for (int j = (casa.getColumn() - 2 < 0 ? 0 : casa.getColumn() - 2); j < (casa.getColumn() + 2 > COLUMNS ? COLUMNS : casa.getColumn() + 2); j++) {
				if (i == casa.getRow() ^ j == casa.getColumn()) {
					Casa adjacente = tabuleiro.getCasas()[i][j];
					
					if (adjacente.getHabilitado() && adjacente.getAgente() == null) {
						adjacentes.add(adjacente);
					}
				}
			}
		}
    	
		return adjacentes.isEmpty();
	}

    /**
     * Valida se a posição escolhida para a raposa não está destinada aos gansos.
     */
    private boolean posicaoValida(int i, int j) {
    	return (i < 4);
    }
    
	private Casa getCasaRaposa() {
        Casa casaRaposa = null;
        
        for (int i=0; i < tabuleiro.getRows(); i++) {
        	for (int j=0; j < tabuleiro.getColumns(); j++) {
    			Casa casa = tabuleiro.getCasas()[i][j];
    			
    			if (AgenteEnum.RAPOSA.equals(casa.getAgente())) {
    				casaRaposa = casa;
    			}
        	}
        }
        
        return casaRaposa;
	}
	
	private Casa getCasaById(String id) {
		Casa casa = null;
		
		if (!(ELIMINATORIA.equals(id))) {
			for (int i=0; i < tabuleiro.getRows(); i++) {
				for (int j=0; j < tabuleiro.getColumns(); j++) {
					if (tabuleiro.getCasas()[i][j].getId().equals(Integer.parseInt(id))) {
						casa = tabuleiro.getCasas()[i][j];
						debug("getCasa");
						debug(casa);
					}
				}
			}
		}
        
        return casa;
	}

	private JogadaValida validaJogada() {
		JogadaValida valida = new JogadaValida();
		valida.setValida(true);
		
		Casa origem = jogada.getOrigem();
		Casa destino = jogada.getDestino();
		
		debug("---------------");
		debug(origem.getAgente());
		debug("---------------");
		
		if (origem.getId().equals(destino.getId())) {
			valida.setValida(false);
			valida.setMensagem("Casa origem não pode ser igual a casa destino !");
			return valida;
		}
		
		if (origem.getAgente() == null) {
			valida.setValida(false);
			valida.setMensagem("Casa origem deve ter um agente selecionado !");
			return valida;
		}
		
		if (destino.getAgente() != null) {
			valida.setValida(false);
			valida.setMensagem("Casa destino deve estar vaga !");
			return valida;
		}
		
		if (!(origem.getAgente().equals(getAgente()))) {
			valida.setValida(false);
			valida.setMensagem("Jogada inválida !");
			return valida;
		}
		
		Integer x = Math.abs(origem.getRow() - destino.getRow());
		Integer y = Math.abs(origem.getColumn() - destino.getColumn());
		
		System.out.println("X: " + x);
		System.out.println("Y: " + y);
		
		if (y.equals(0) ^ x.equals(0)) {
			if (AgenteEnum.RAPOSA.equals(getAgente())) {
				Integer direction = !(x.equals(0)) ? x : y;
				
				System.out.println("DIRECTION: " + direction);
				
				if (direction.equals(JOGADA_ELIMINATORIA)) {
					Casa casaEliminada = getCasaEliminada(jogada);
					
					if (AgenteEnum.GANSO.equals(casaEliminada.getAgente())) {
						jogada.setEliminada(casaEliminada);

						removeGanso(casaEliminada);
						qtdGansosTabuleiro--;
					} else {
						valida.setValida(false);
						valida.setMensagem("Movimento não permitido !");
						return valida;
					}
				} else if (!(direction.equals(JOGADA_SIMPLES))) {
					valida.setValida(false);
					valida.setMensagem("Movimento não permitido !");
					return valida;
				}
			} else if (AgenteEnum.GANSO.equals(getAgente())) {
				if (!(x.equals(JOGADA_SIMPLES) && y.equals(0)) ^ (y.equals(JOGADA_SIMPLES) && x.equals(0))) {
					valida.setValida(false);
					valida.setMensagem("Movimento não permitido !");
					return valida;
				}
			}
		} else {
			valida.setValida(false);
			valida.setMensagem("Movimento não permitido !");
			return valida;
		}

		return valida;
	}
	
	private void removeGanso(Casa casaEliminada) {
		casaEliminada.setAgente(null);
	}

	private Casa getCasaEliminada(Jogada jogada) {
		Casa origem = jogada.getOrigem();
		Casa destino = jogada.getDestino();
		
		Integer x = null;
		Integer y = null;
		
		if (origem.getRow().equals(destino.getRow())) {
			x = origem.getRow();
			y = (origem.getColumn() + destino.getColumn()) / 2;
		} else if (origem.getColumn().equals(destino.getColumn())) {
			x = (origem.getRow() + destino.getRow()) / 2;
			y = origem.getColumn();
		}
		
		Casa casa = null;
		
		for (int i = 0; i < tabuleiro.getRows(); i++) {
			for (int j = 0; j < tabuleiro.getColumns(); j++) {
				if (i == x.intValue() && j == y.intValue()) {
					casa = tabuleiro.getCasas()[i][j];
				}
			}
		}
		
		return casa;
	}

	private void moveAgentes(Jogada jogada) {
		Casa origem = jogada.getOrigem();
		Casa destino = jogada.getDestino();
		
		destino.setAgente(origem.getAgente());
		origem.setAgente(null);
		
		if (jogada.getEliminada() != null) {
			removeGanso(jogada.getEliminada());
		}
	}
	
	private void setupAgentes(AgenteEnum agente, AgenteEnum adversario) {
		setEstadoBotoes(false);
		
		setAgente(agente);
		setAgenteAdversario(adversario);
		
		populaGansos();
		
		lblAgente.setText(agente.getMeaning());
		
		if (AgenteEnum.RAPOSA.equals(agente)) {
			JOptionPane.showMessageDialog(GameView.this, "Para continuar é necessário selecionar o local da raposa");
		}
	}
	
	public void dataReceived(String data) {
		debug("DATA GAME VIEW");
		debug(data);
		
		String[] comandos = data.split(COMMAND_SEPARATOR);
		
		debug(comandos[0]);
		
		CommandEnum command = CommandEnum.getValue(comandos[0]);
		
		switch (command) {
			case BTN_RAPOSA:
				setupAgentes(AgenteEnum.GANSO, AgenteEnum.RAPOSA);
				
				break;
	
			case BTN_GANSO:
				setupAgentes(AgenteEnum.RAPOSA, AgenteEnum.GANSO);
				
				break;
				
			case SETUP_RAPOSA:
				Casa casa = getCasaById(comandos[1]);
				casa.setAgente(AgenteEnum.RAPOSA);
				
				raposaSelecionada = true;
				
				setAgenteJogada(AgenteEnum.GANSO);
				
				break;
				
			case SETUP_GANSO:
				break;
				
			case JOGADA:
				Jogada j = new Jogada();
				j.setAgente(getAgenteAdversario());
				j.setOrigem(getCasaById(comandos[1]));
				j.setDestino(getCasaById(comandos[2]));
				j.setEliminada(getCasaById(comandos[3]));
				
				moveAgentes(j);
				
				setAgenteJogada(AgenteEnum.getValue(comandos[4]));
				
				break;
				
			case BTN_PORTA:
				setupAgenteAdversario = true;
				
				/*
				 *  necessario para que o segundo escolhido tenha a 
				 *  informacao do setup do oponente
				 */
				if (reenvio) {
					sendCommand(CommandEnum.BTN_PORTA);
					reenvio = false;
				}
				
				setEstadoBotoes(true);
				
				break;
			case FIM_JOGO: 
				JOptionPane.showMessageDialog(this, "Seu adverário venceu", "Fim de Jogo", 1);
				fimJogo = true;
				
				break;
				
			default:
				break;
		}
	}

	private void setEstadoBotoes(boolean b) {
		btnRaposa.setEnabled(b);
		btnGansos.setEnabled(b);
	}

	public void sendCommand(CommandEnum command) {
		String msg = command.getKey();
		
		switch (command) {
			case BTN_RAPOSA:
				break;
				
			case BTN_GANSO: 
				break;
				
			case SETUP_RAPOSA: 
				msg += COMMAND_SEPARATOR 
				     + getCasaRaposa().getId().toString();
				
				break;
				
			case SETUP_GANSO:
				break;
				
			case JOGADA:
				msg += COMMAND_SEPARATOR 
				     + jogada.getOrigem().getId() 
				     + COMMAND_SEPARATOR 
				     + jogada.getDestino().getId() 
				     + COMMAND_SEPARATOR 
				     + (jogada.getEliminada() != null ? jogada.getEliminada().getId() : ELIMINATORIA)
				     + COMMAND_SEPARATOR
				     + getAgenteJogada().getKey(); 
				
				break;
				
			case BTN_PORTA:
				break;
				
			case FIM_JOGO:
				msg += COMMAND_SEPARATOR
				     + getAgente().getKey();
				
				break;
				
			default :
				break;
		}
		
		debug("MSG: " + msg);
		
		porta.sendCommand(msg);
	}
	
	/**
	 * Desabilitar as casas referentes aos cantos do tabuleiro
	 * (0,0), (0,1), (1,0), (1,1)
	 * (0,5), (0,6), (1,5), (1,6) 
	 * (5,0), (5,1), (6,0), (6,1) 
	 * (5,5), (5,6), (6,5), (6,6) 
	 * @param i
	 * @param j
	 * @return
	 */
	private boolean isVisible(int i, int j) {
		return !((i == 0 || i == 1 || i == 5 || i == 6) && (j == 0 || j == 1 || j == 5 || j == 6));
	}
	
	public AgenteEnum getAgente() {
		return agente;
	}

	public void setAgente(AgenteEnum agente) {
		this.agente = agente;
	}
	
	public AgenteEnum getAgenteAdversario() {
		return agenteAdversario;
	}
	
	public void setAgenteAdversario(AgenteEnum agenteAdversario) {
		this.agenteAdversario = agenteAdversario;
	}

	public AgenteEnum getAgenteJogada() {
		return agenteJogada;
	}

	public void setAgenteJogada(AgenteEnum agenteJogada) {
		this.agenteJogada = agenteJogada;
		
		if (setupsInformados()) {
			lblJogadaAtual.setText(agenteJogada.getMeaning());
		}
	}
	
	public boolean agentesInformados() {
		return getAgente() != null && getAgenteAdversario() != null;
	}
	
	public void setSetupAgente(boolean bool) {
		setupAgente = bool;
	}
	
	public boolean isSetupAgente() {
		return setupAgente;
	}
	
	public void setSetupAgenteAdversario(boolean bool) {
		this.setupAgenteAdversario = bool;
	}
	
	public boolean isSetupAgenteAdversario() {
		return setupAgenteAdversario;
	}
	
	public boolean setupsInformados() {
		return isSetupAgente() && isSetupAgenteAdversario();
	}
	
	public GamePorta getPorta() {
		return porta;
	}
	
	private static void debug(Object log) {
		if (debugging) {
			System.out.println(log);
		}
	}
	
}
